use blip_buf::BlipBuf;

use super::{
    interface::IAudioPlayer, noisechannel::NoiseChannel, squarechannel::SquareChannel,
    wavechannel::WaveChannel,
};

pub(crate) const WAVE_PATTERN: [[i32; 8]; 4] = [
    [-1, -1, -1, -1, 1, -1, -1, -1],
    [-1, -1, -1, -1, 1, 1, -1, -1],
    [-1, -1, 1, 1, 1, 1, -1, -1],
    [1, 1, 1, 1, -1, -1, 1, 1],
];

pub(crate) const CLOCKS_PER_SECOND: u32 = 1 << 22;

pub(crate) const CLOCKS_PER_FRAME: u32 = CLOCKS_PER_SECOND / 512;

// this should be less than blip_buf::MAX_FRAME
pub(crate) const OUTPUT_SAMPLE_COUNT: usize = 2000;

pub(crate) const SWEEP_DELAY_ZERO_PERIOD: u8 = 8;

// Additional delay on trigger of the wave channel (channel 3)
// In other emulators it is 6
// but we need 4 since we run the wave after delay == 0,
// instead of at delay == 0
pub(crate) const WAVE_INITIAL_DELAY: u32 = 4;

pub(crate) struct Sound {
    on: bool,
    time: u32,
    perv_time: u32,
    next_time: u32,
    frame_step: u8,
    output_period: u32,
    // channels stuff
    chan1: SquareChannel,
    chan2: SquareChannel,

    chan3: WaveChannel,
    chan4: NoiseChannel,

    volume_left: u8,
    volume_right: u8,
    reg_vin_to_so: u8,
    reg_ff25: u8,
    need_sync: bool,
    dmg_mode: bool,
    player: Box<dyn IAudioPlayer>,
}

impl Sound {
    pub(crate) fn new_dmg(player: Box<dyn IAudioPlayer>) -> Self {
        Self::private_new(player, true)
    }

    pub(crate) fn new_cgb(player: Box<dyn IAudioPlayer>) -> Self {
        Self::private_new(player, false)
    }

    fn private_new(player: Box<dyn IAudioPlayer>, dmg_mode: bool) -> Self {
        let blipbuf1 = create_blipbuf(player.samples_rate());
        let blipbuf2 = create_blipbuf(player.samples_rate());
        let blipbuf3 = create_blipbuf(player.samples_rate());
        let blipbuf4 = create_blipbuf(player.samples_rate());

        let output_period =
            (OUTPUT_SAMPLE_COUNT as u64 * CLOCKS_PER_SECOND as u64) / player.samples_rate() as u64;

        Self {
            on: false,
            time: 0,
            perv_time: 0,
            next_time: CLOCKS_PER_FRAME,
            frame_step: 0,
            output_period: output_period as u32,
            chan1: SquareChannel::new(blipbuf1, true),
            chan2: SquareChannel::new(blipbuf2, false),
            chan3: WaveChannel::new(blipbuf3, dmg_mode),
            chan4: NoiseChannel::new(blipbuf4),

            volume_left: 7,
            volume_right: 7,
            reg_vin_to_so: 0x00,
            reg_ff25: 0x00,
            need_sync: false,
            dmg_mode,
            player,
        }
    }

    pub(crate) fn rb(&mut self, addr: u16) -> u8 {
        self.run();
        match addr {
            0xFF10..=0xFF14 => self.chan1.rb(addr),
            0xFF16..=0xFF19 => self.chan2.rb(addr),
            0xFF1A..=0xFF1E => self.chan3.rb(addr),
            0xFF20..=0xFF23 => self.chan4.rb(addr),

            0xFF24 => ((self.volume_right & 7) << 4) | (self.volume_left & 7) | self.reg_vin_to_so,

            0xFF25 => self.reg_ff25,
            0xFF26 => {
                (if self.on { 0x80 } else { 0x00 }
                    | 0x70
                    | if self.chan4.on() { 0x8 } else { 0x0 }
                    | if self.chan3.on() { 0x4 } else { 0x0 }
                    | if self.chan2.on() { 0x2 } else { 0x0 }
                    | if self.chan1.on() { 0x1 } else { 0x0 })
            }

            0xFF30..=0xFF3F => self.chan3.rb(addr),
            _ => 0xFF,
        }
    }

    pub(crate) fn wb(&mut self, addr: u16, value: u8) {
        if !self.on {
            // allow writes to the length register when in DMG mode
            if self.dmg_mode {
                match addr {
                    0xFF11 => self.chan1.wb(addr, value & 0x3F, self.frame_step),
                    0xFF16 => self.chan2.wb(addr, value & 0x3F, self.frame_step),
                    0xFF1B => self.chan3.wb(addr, value, self.frame_step),
                    0xFF20 => self.chan4.wb(addr, value & 0x3F, self.frame_step),
                    _ => unimplemented!("not supported addr"),
                }
            }

            // only allow further writes to the control register
            if addr != 0xFF26 {
                return;
            }
        }

        self.run();

        match addr {
            0xFF10..=0xFF14 => self.chan1.wb(addr, value, self.frame_step),
            0xFF16..=0xFF19 => self.chan2.wb(addr, value, self.frame_step),
            0xFF1A..=0xFF1E => self.chan3.wb(addr, value, self.frame_step),
            0xFF20..=0xFF23 => self.chan4.wb(addr, value, self.frame_step),

            0xFF24 => {
                self.volume_left = value & 0x7;
                self.volume_right = (value >> 4) & 0x7;
                self.reg_vin_to_so = value & 0x88;
            }

            0xFF25 => self.reg_ff25 = value,
            0xFF26 => {
                let turn_on = value & 0x80 == 0x80;
                if self.on && turn_on == false {
                    // reset all registers to 0 when turn off
                    for i in 0xFF10..=0xFF25 {
                        self.wb(addr, 0);
                    }
                }

                if !self.on && turn_on {
                    // reset frame step when turning on
                    self.frame_step = 0;
                }

                self.on = turn_on;
            }
            0xFF30..=0xFF3F => self.chan3.wb(addr, value, self.frame_step),
            _ => unimplemented!("not supported addr"),
        }
    }

    pub fn do_cycle(&mut self, cycles: u32) {
        if !self.on {
            return;
        }
        self.time += cycles;

        if self.time >= self.output_period {
            self.do_output();
        }
    }

    fn do_output(&mut self) {
        self.run();
        // debug
        debug_assert!(self.time == self.perv_time);

        self.chan1.blip.end_frame(self.time);
        self.chan2.blip.end_frame(self.time);
        self.chan3.blip.end_frame(self.time);
        self.chan4.blip.end_frame(self.time);

        self.next_time -= self.time;
        self.time = 0;
        self.perv_time = 0;

        if !self.need_sync || self.player.underflowed() {
            self.need_sync = false;
            self.mix_buffers();
        } else {
            // prevent the BlipBuf's from filling up and triggering an assertion
            self.clear_buffers();
        }
    }

    pub fn sync(&mut self) {
        self.need_sync = true;
    }

    fn run(&mut self) {
        while self.next_time <= self.time {
            self.chan1.run(self.perv_time, self.next_time);
            self.chan2.run(self.perv_time, self.next_time);
            self.chan3.run(self.perv_time, self.next_time);
            self.chan4.run(self.perv_time, self.next_time);

            if self.frame_step % 2 == 0 {
                self.chan1.step_length();
                self.chan2.step_length();
                self.chan3.step_length();
                self.chan4.step_length();
            }

            if self.frame_step % 4 == 2 {
                self.chan1.step_sweep();
            }

            if self.frame_step == 7 {
                self.chan1.volume_envelope.step();
                self.chan2.volume_envelope.step();
                self.chan4.volume_envelope.step();
            }

            self.frame_step = (self.frame_step + 1) % 8;
            self.perv_time = self.next_time;
            self.next_time += CLOCKS_PER_FRAME;
        }

        if self.perv_time != self.time {
            self.chan1.run(self.perv_time, self.time);
            self.chan2.run(self.perv_time, self.time);
            self.chan3.run(self.perv_time, self.time);
            self.chan4.run(self.perv_time, self.time);

            self.perv_time = self.time;
        }
    }
    fn mix_buffers(&mut self) {
        let sample_count = self.chan1.blip.samples_avail() as usize;
        // debug
        debug_assert!(sample_count == self.chan2.blip.samples_avail() as usize);
        debug_assert!(sample_count == self.chan3.blip.samples_avail() as usize);
        debug_assert!(sample_count == self.chan4.blip.samples_avail() as usize);

        let mut outputted = 0;

        let left_vol = (self.volume_left as f32 / 7.0) * (1.0 / 15.0) * 0.25;
        let right_vol = (self.volume_right as f32 / 7.0) * (1.0 / 15.0) * 0.25;

        while outputted < sample_count {
            const CAPACITY: usize = OUTPUT_SAMPLE_COUNT + 10;
            let buf_left = &mut [0f32; CAPACITY];
            let buf_right = &mut [0f32; CAPACITY];
            let buf = &mut [0i16; CAPACITY];

            let count1 = self.chan1.blip.read_samples(buf, false);

            for (i, v) in buf[..count1].iter().enumerate() {
                if self.reg_ff25 & 0x10 == 0x10 {
                    let tmp_v = *v as f32;
                    buf_left[i] += tmp_v * left_vol;
                }
                if self.reg_ff25 & 0x01 == 0x01 {
                    let tmp_v = *v as f32;
                    buf_right[i] += tmp_v * right_vol;
                }
            }

            let count2 = self.chan2.blip.read_samples(buf, false);

            for (i, v) in buf[..count2].iter().enumerate() {
                if self.reg_ff25 & 0x20 == 0x20 {
                    let tmp_v = *v as f32;
                    buf_left[i] = tmp_v * left_vol;
                }
                if self.reg_ff25 & 0x02 == 0x02 {
                    let tmp_v = *v as f32;
                    buf_right[i] += tmp_v * right_vol;
                }
            }

            // channel3 is the WaveChannel,
            // that outputs samples with a 4x
            // increase in amplitude in order to
            // avoid a loss of percision
            let count3 = self.chan3.blip.read_samples(buf, false);

            for (i, v) in buf[..count3].iter().enumerate() {
                if self.reg_ff25 & 0x40 == 0x40 {
                    let tmp_v = *v as f32 / 4.0;
                    buf_left[i] += tmp_v * left_vol;
                }
                if self.reg_ff25 & 0x04 == 0x04 {
                    let tmp_v = *v as f32 / 4.0;
                    buf_right[i] += tmp_v * right_vol;
                }
            }

            let count4 = self.chan4.blip.read_samples(buf, false);

            for (i, v) in buf[..count4].iter().enumerate() {
                if self.reg_ff25 & 0x80 == 0x80 {
                    let tmp_v = *v as f32;
                    buf_left[i] += tmp_v * left_vol;
                }
                if self.reg_ff25 & 0x08 == 0x08 {
                    let tmp_v = *v as f32;
                    buf_right[i] += tmp_v * right_vol;
                }
            }

            // debug
            debug_assert!(count1 == count2);
            debug_assert!(count1 == count3);
            debug_assert!(count1 == count4);

            self.player.play(&buf_left[..count1], &buf_right[..count1]);

            outputted += count1;
        }
    }
    fn clear_buffers(&mut self) {
        self.chan1.blip.clear();
        self.chan2.blip.clear();
        self.chan3.blip.clear();
        self.chan4.blip.clear();
    }
}

fn create_blipbuf(samples_rate: u32) -> BlipBuf {
    // create a BlipBuf which can hold OUTPUT_SAMPLE_COUNT + 1 samples
    // not sure why the +1 is needed
    // may need to correct the constant instead

    let mut blipbuf = BlipBuf::new((OUTPUT_SAMPLE_COUNT + 1) as u32);
    blipbuf.set_rates(CLOCKS_PER_SECOND as f64, samples_rate as f64);
    blipbuf
}
